src/pages/og-image/[slug].png.ts 3.0 K raw
1
import type { APIContext, GetStaticPathsResult } from "astro";
2
import { getCollection, getEntryBySlug } from "astro:content";
3
import satori, { SatoriOptions } from "satori";
4
import { html } from "satori-html";
5
import { Resvg } from "@resvg/resvg-js";
6
import siteConfig from "@/site-config";
7
import { getFormattedDate } from "@/utils";
8
9
const monoFontReg = await fetch(
10
	"https://api.fontsource.org/v1/fonts/roboto-mono/latin-400-normal.ttf"
11
);
12
13
const monoFontBold = await fetch(
14
	"https://api.fontsource.org/v1/fonts/roboto-mono/latin-700-normal.ttf"
15
);
16
17
const ogOptions: SatoriOptions = {
18
	width: 1200,
19
	height: 630,
20
	// debug: true,
21
	embedFont: true,
22
	fonts: [
23
		{
24
			name: "Roboto Mono",
25
			data: await monoFontReg.arrayBuffer(),
26
			weight: 400,
27
			style: "normal",
28
		},
29
		{
30
			name: "Roboto Mono",
31
			data: await monoFontBold.arrayBuffer(),
32
			weight: 700,
33
			style: "normal",
34
		},
35
	],
36
};
37
38
const markup = (title: string, pubDate: string, description: string) => html`<div
39
	tw="flex flex-col w-full h-full bg-[#141617] text-[#ddc7a1]"
40
>
41
	<div tw="flex flex-col flex-1 w-full p-10 justify-center">
42
		<p tw="text-2xl mb-6">${pubDate}</p>
43
		<h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1>
44
		<h2 tw="text-2xl font-bold leading-snug text-white">${description}</h2>
45
	</div>
46
	<div tw="flex items-center justify-between w-full p-10 border-t border-[#7c6f64] text-xl">
47
		<div tw="flex items-center">
48
			<svg height="60" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
49
				<path
50
					d="M20.1465 448.094H479L350.926 226.37L311.281 258.273L275.108 232.751L249.573 258.273L215.528 232.751L193.185 258.273L151.291 221.053L20.1465 448.094Z"
51
					fill="#7c6f64"
52
				/>
53
				<path
54
					d="M249.573 50.9053L151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37L249.573 50.9053Z"
55
					fill="#a89984"
56
				/>
57
				<path
58
					d="M151.291 221.053L20.1465 448.094H479L350.926 226.37M151.291 221.053L249.573 50.9053L350.926 226.37M151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37"
59
					stroke="black"
60
					stroke-width="7"
61
				/>
62
				<line x1="265.341" y1="167.541" x2="294.587" y2="218.169" stroke="black" stroke-width="5" />
63
			</svg>
64
			<p tw="ml-3 font-semibold text-3xl">${siteConfig.title}</p>
65
		</div>
66
	</div>
67
</div>`;
68
69
export async function get({ params: { slug } }: APIContext) {
70
	const post = await getEntryBySlug("post", slug!);
71
	const title = post?.data.title ?? siteConfig.title;
72
	const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), {
73
		weekday: "long",
74
	});
75
	const description = post?.data.description ?? siteConfig.title;
76
	const svg = await satori(markup(title, postDate, description), ogOptions);
77
	const png = new Resvg(svg).render().asPng();
78
	return {
79
		body: png,
80
		encoding: "binary",
81
	};
82
}
83
84
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
85
	const posts = await getCollection("post");
86
	return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } }));
87
}